/*
 * File:    ConvertTagCache.java
 * Created: 04-Feb-2009
 * Version: $Id$
 *
 * COPYRIGHT (C) 2009, Bitgate Mobile, LLC.  All Rights Reserved.
 *
 * software@bitgatemobile.com
 */

package com.webplasm.render.convert;

import static com.bitgate.util.debug.Debug.debug;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.jar.JarEntry;
import java.util.jar.JarInputStream;

import com.webplasm.render.tags.TagInterface;
import com.webplasm.render.tags.WrappedNode;

/**
 * This is the cache that reads through all of the available tags in the system, and pre-caches them so that
 * rendered pages are done faster.
 * 
 * @author kenji
 * @since Webplasm 2
 */
public class ConvertTagCache {
	private static final String TAG_CLASS_NAME_FILTER = "com/webplasm/render/tags/";
	private static final ConvertTagCache _default = new ConvertTagCache();
	
	private static final Object TAG_NODE_PLACEHOLDER = new Object();
	
	// Namespace:Name, Classname for Instantiation
	private Map<String, String> tags;
	private Map<String, Object> tagNodes;
	
	@SuppressWarnings("unchecked")
	private ConvertTagCache() {
		tags = new HashMap<String, String>();
		tagNodes = new HashMap<String, Object>();
		
		debug("Populating tag cache.");
		
		// Yuck.  I hate doing this this way, but it seems the ClassLoader is too limiting to tell me the classes
		// that are currently loaded in memory.
		try {
			for(String classpathEntry : System.getProperty("java.class.path").split(System.getProperty("path.separator"))) {
				if (classpathEntry.endsWith(".jar")) {
					File jar = new File(classpathEntry);
					JarInputStream is = new JarInputStream(new FileInputStream(jar));
					JarEntry entry;
					
					while((entry = is.getNextJarEntry()) != null) {
						if (entry.getName().startsWith(TAG_CLASS_NAME_FILTER) && entry.getName().endsWith(".class")) {
							String classEntry = entry.getName().substring(0, entry.getName().length() - 6).replaceAll("\\/", ".");

							try {
								Class<TagInterface> clazz = (Class<TagInterface>) Class.forName(classEntry);
								Class<?>[] interfaces = clazz.getInterfaces();
								
								for(Class<?> iface : interfaces) {
									if (iface.getName().toString().endsWith("TagInterface")) {
										String className = classEntry.substring(classEntry.lastIndexOf('.') + 1);
										TagInterface tInterface = clazz.newInstance();
										String namespaceTag = (tInterface.getNamespace() + ":" + className).toLowerCase();
										
										debug("Class '" + namespaceTag + "' is a tag interface for '" + classEntry + "'");
										tags.put(namespaceTag, classEntry);
										
										List<String> subTags = tInterface.getManagedNodes();
										
										if (subTags != null) {
											for(String subTag : subTags) {
												tagNodes.put(subTag, TAG_NODE_PLACEHOLDER);
											}
										}
									}
								}
							} catch(Exception ex) {
								debug("Unable to load class '" + classEntry + "': " + ex.getMessage() + " (" + ex + ")");
							}
						}
					}
				}
			}
		} catch(IOException ex) {
			debug("Unable to get classes: " + ex.getMessage());
		}
	}
	
	/**
	 * Returns the default instance of this class.
	 * 
	 * @return <code>static final ConvertTagCache</code> object.
	 */
	public static final ConvertTagCache getDefault() {
		return _default;
	}
	
	/**
	 * Returns a set of all of the tag cache names, so they can easily be iterated against.
	 * 
	 * @return <code>Set&lt;String&gt;</code> containing all of the tag names.
	 */
	public Set<String> getTagNames() {
		return tags.keySet();
	}
	
	/**
	 * Returns a list of all of the subtag names implemented by each tag.
	 * 
	 * @return <code>Set&lt;String&gt;</code> containing all of the subtags.
	 */
	public Set<String> getSubtagNames() {
		return tagNodes.keySet();
	}
	
	/**
	 * Returns the <code>WrappedNode</code> for the tag class specified by tag name.
	 * 
	 * @param tagname The tag name to look up.
	 * @return <code>Class&lt;WrappedNode&gt;</code> object if found, <code>null</code> otherwise.
	 */
	@SuppressWarnings("unchecked")
	public Class<WrappedNode> getTagClass(String tagname) {
		if (tags.get(tagname.toLowerCase()) == null) {
			return null;
		}
		
		try {
			return (Class<WrappedNode>) Class.forName(tags.get(tagname.toLowerCase()));
		} catch(Exception ex) {
			debug("Unable to create new renderable tag: (" + ex.getClass().getName() + "): " + ex.getMessage());
		}
		
		return null;
	}
}